﻿#region The MIT License (MIT)
// The MIT License (MIT)
// Copyright (c) 2010 - 2013 Sovacode LLC
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions
// of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Xml.Serialization;
using Sovacode.RubiksCube.Common.Attributes;
using Sovacode.RubiksCube.Common.Base;
using Sovacode.RubiksCube.Common.Managers.Command;
using Sovacode.RubiksCube.Common.Managers.Settings;
using Sovacode.RubiksCube.Common.Managers.Validation;
using Sovacode.RubiksCube.Common.Service;
using Sovacode.RubiksCube.Common.Service.Empty;
using Sovacode.RubiksCube.Common.Service.Progress;
using Sovacode.RubiksCube.Common.Text;
using CommandManager = Sovacode.RubiksCube.Common.Managers.Command.CommandManager;

namespace Sovacode.RubiksCube.Common.ViewModel
{
    /// <summary>
    /// Top View Model
    /// </summary>
    [ValidateByManager]
    public class ViewModelTop : NotifyValidatedObject, IViewModelTop, ICommandExporter
    {
        #region Private Fields

        #region Service and Managers

        /// <summary>
        /// The settings manager
        /// </summary>
        [NonSerialized]
        [XmlIgnore]
        private ISettingsManager _settingsManager;

        /// <summary>
        /// The window service
        /// </summary>
        [NonSerialized] 
        [XmlIgnore] 
        private IWindowService _windowService = new EmptyWindowService();

        /// <summary>
        /// The command manager
        /// </summary>
        [NonSerialized] 
        [XmlIgnore] 
        private ICommandManager _commandManager;

        /// <summary>
        /// The progress service
        /// </summary>
        [NonSerialized]
        [XmlIgnore] 
        private readonly IProgressService _progressService = new ProgressService();

        #endregion

        /// <summary>
        /// The _can refresh
        /// </summary>
        [NonSerialized]
        [XmlIgnore] 
        private bool _canRefresh = true;

        #endregion

        #region Service and Managers

        /// <summary>
        /// Gets the progress service.
        /// </summary>
        /// <value>The progress service.</value>
        [Browsable(false)]
        public IProgressService ProgressService
        {
            get { return _progressService; }
        }

        /// <summary>
        /// Window service <see cref="IWindowService" />
        /// </summary>
        /// <value>The window service.</value>
        [Browsable(false)]
        public IWindowService WindowService
        {
            get { return _windowService ?? (_windowService = new EmptyWindowService()); }
            set
            {
                _windowService = value;

                SettingsManager.ShowError = ShowError;
                CommandManager.ShowError = ShowError;

                OnPropertyChanged(() => WindowService);
            }
        }

        /// <summary>
        /// Configuration Manager <see cref="ISettingsManager" />
        /// </summary>
        /// <value>The settings manager.</value>
        [Browsable(false)]
        public ISettingsManager SettingsManager
        {
            get { return _settingsManager ?? (_settingsManager = new EmptySettingsManager { ShowError = s => ShowError(s) }); }
            set
            {
                _settingsManager = value;

                _settingsManager.ShowError = ShowError;

                LoadSettings();

                OnPropertyChanged(() => SettingsManager);
            }
        }

        /// <summary>
        /// Command Manager <see cref="ICommandManager" />
        /// </summary>
        /// <value>The command manager.</value>
        [Browsable(false)]
        public ICommandManager CommandManager
        {
            get { return _commandManager ?? (_commandManager = new CommandManager { ShowError = s => ShowError(s)}); }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Can do Refresh
        /// </summary>
        /// <value><c>true</c> if this instance can refresh; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        protected virtual bool CanRefresh
        {
            get { return _canRefresh; }

            set
            {
                if (_canRefresh == value)
                {
                    return;
                }

                _canRefresh = value;

                OnPropertyChanged(() => CanRefresh);
            }
        }

        #endregion

        #region Methods

        #region Self-tested diagnostic

        /// <summary>
        /// Display Information Dialog about broken properties of Managers
        /// </summary>
        /// <param name="invalidProperties">List of properties <see cref="IReadOnlyCollection{T}" /></param>
        /// <param name="failedMessage">Fail message</param>
        /// <param name="successMessage">Success message</param>
        private void ShowInvalidPropertiesList(IReadOnlyCollection<string> invalidProperties, string failedMessage = "", string successMessage = "")
        {
            if (invalidProperties == null || invalidProperties.Count == 0)
            {
                ShowMessage(successMessage);

                return;
            }

            var commandsText = new StringBuilder();

            commandsText.AppendLine(failedMessage);

            foreach (var commandName in invalidProperties)
            {
                commandsText.AppendLine(commandName);
            }

            ShowError(commandsText.ToString());
        }

        /// <summary>
        /// Displays Invalid Commands
        /// </summary>
        [CommandInfo("ShowInvalidCommands", "ShowInvalidCommands", "Ctrl+0", "Shows dialog with invalid commands")]
        private void ShowInvalidCommands()
        {
            ShowInvalidPropertiesList(InvalidCommands, CategorizedBy.CommandManagerMessages.FailedMessage, CategorizedBy.CommandManagerMessages.SuccessMessage);
        }

        /// <summary>
        /// Displays Invalid Settings
        /// </summary>
        [CommandInfo("ShowInvalidSettings", "ShowInvalidSettings", "Ctrl+1", "Shows dialog with invalid settings")]
        private void ShowInvalidSettings()
        {
            ShowInvalidPropertiesList(InvalidSettings, CategorizedBy.SettingsManager.FailedMessage, CategorizedBy.SettingsManager.SuccessMessage);
        }

        /// <summary>
        /// Displays Invalid Validation Rules
        /// </summary>
        [CommandInfo("ShowInvalidValidation", "ShowInvalidValidators", "Ctrl+2", "Shows dialog with invalid validation")]
        private void ShowInvalidValidation()
        {
            ShowInvalidPropertiesList(InvalidValidators, CategorizedBy.ValidationManagerMessages.FailedMessage, CategorizedBy.ValidationManagerMessages.SuccessMessage);
        }

        #endregion


        #region Save/Load/Refresh & Update Command Manager

        /// <summary>
        /// Save settings
        /// </summary>
        [CommandInfo("SaveSettings", "SaveSettings", "", "Save settings")]
        protected virtual void SaveSettings()
        {
            SettingsManager.SaveSettings();
        }

        /// <summary>
        /// Load Settings
        /// </summary>
        [CommandInfo("LoadSettings", "LoadSettings", "", "Load settings")]
        public virtual void LoadSettings()
        {

        }

        /// <summary>
        /// Refresh
        /// </summary>
        [CommandInfo("Refresh", "Refresh", "F5", "Refresh view model")]
        protected virtual void Refresh()
        {
            System.Windows.Input.CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Clear all registered commands and re-registered
        /// </summary>
        [CommandInfo("UpdateCommands", "UpdateCommands", "", "Re-Register and Updates commands")]
        protected virtual void UpdateCommands()
        {
            if (!CommandManager.IsEmpty)
            {
                CommandManager.Clear();
            }

            RegisterCommands();
        }

        /// <summary>
        /// Show settings
        /// </summary>
        [CommandInfo("ShowSettings", "ShowSettings", "", "Shows dialog with settings")]
        private void ShowSettings()
        {
            if (SettingsManager.ShowSettings(this) == WindowResult.Save)
            {
                SettingsManager.SaveSettings();
                LoadSettings();
            }
        }

        #endregion

        #region Export Commands

        /// <summary>
        /// Quick export commands to XML.
        /// </summary>
        [CommandInfo("QuickExportCommandsToXml", "QuickExportCommandsToXml", "Ctrl+4", "Export commands information to XML")]
        private void QuickExportCommandsToXml()
        {
            ExportCommands();
        }

        /// <summary>
        /// Exports the commands to specific XML.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        [CommandInfo("ExportCommandsToXml", "ExportCommandsToXml", "", "Export commands information to specific XML")]
        private void ExportCommandsToXml(string fileName)
        {
            ExportCommands(fileName);
        }

        #endregion


        /// <summary>
        /// Method where the commands should be registered.
        /// </summary>
        protected virtual void RegisterCommands()
        {
            RegisterCommand(CategorizedBy.SystemCommands.ShowInvalidCommandsCommand, cmd => ShowInvalidCommands());
            RegisterCommand(CategorizedBy.SystemCommands.ShowInvalidSettingsCommand, cmd => ShowInvalidSettings());
            RegisterCommand(CategorizedBy.SystemCommands.ShowInvalidValidatorsCommand, cmd => ShowInvalidValidation());

            RegisterCommand(CategorizedBy.SystemCommands.LoadSettingsCommand, cmd => LoadSettings());
            RegisterCommand(CategorizedBy.SystemCommands.SaveSettingsCommand, cmd => SaveSettings());
            RegisterCommand(CategorizedBy.SystemCommands.ShowSettingshCommand, cmd => ShowSettings());

            RegisterCommand(CategorizedBy.SystemCommands.RefreshCommand, cmd => Refresh(), param => CanRefresh);
            RegisterCommand(CategorizedBy.SystemCommands.UpdateCommandsCommand, cmd => UpdateCommands());

            RegisterCommand(CategorizedBy.SystemCommands.QuickExportCommandsToXml, cmd => QuickExportCommandsToXml());
            RegisterCommand<string>(CategorizedBy.SystemCommands.ExportCommandsToXml, ExportCommandsToXml);
        }

        #endregion

        #region Register Command

        /// <summary>
        /// Registration of Command
        /// </summary>
        /// <param name="commandName">Command Name</param>
        /// <param name="execute"><see cref="Action{T}" /></param>
        public void RegisterCommand(string commandName, Action<object> execute)
        {
            CommandManager.RegisterCommand(commandName, execute);

            OnPropertiesChanged(() => InvalidCommands, () => ValidCommands, () => AllCommands);
            OnPropertiesChanged(() => InvalidCommandsCount, () => ValidCommandsCount, () => CommandsCount);
        }

        /// <summary>
        /// Registration of Command
        /// </summary>
        /// <typeparam name="T">Type of command argument</typeparam>
        /// <param name="commandName">Command Name</param>
        /// <param name="execute"><see cref="Action{T}" /></param>
        public void RegisterCommand<T>(string commandName, Action<T> execute)
        {
            CommandManager.RegisterCommand<T>(commandName, execute);

            OnPropertiesChanged(() => InvalidCommands, () => ValidCommands, () => AllCommands);
            OnPropertiesChanged(() => InvalidCommandsCount, () => ValidCommandsCount, () => CommandsCount);
        }

        /// <summary>
        /// Registration of Command
        /// </summary>
        /// <param name="commandName">Command Name</param>
        /// <param name="execute"><see cref="Action{T}" /></param>
        /// <param name="canExecute"><see cref="Predicate{T}" /></param>
        public void RegisterCommand(string commandName, Action<object> execute, Predicate<object> canExecute)
        {
            CommandManager.RegisterCommand(commandName, execute, canExecute);

            OnPropertiesChanged(() => InvalidCommands, () => ValidCommands, () => AllCommands);
            OnPropertiesChanged(() => InvalidCommandsCount, () => ValidCommandsCount, () => CommandsCount);
        }

        /// <summary>
        /// Registration of Command
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="commandName">Command Name</param>
        /// <param name="execute"><see cref="Action{T}" /></param>
        /// <param name="canExecute"><see cref="Predicate{T}" /></param>
        public void RegisterCommand<T>(string commandName, Action<T> execute, Predicate<object> canExecute)
        {
            CommandManager.RegisterCommand<T>(commandName, execute, canExecute);

            OnPropertiesChanged(() => InvalidCommands, () => ValidCommands, () => AllCommands);
            OnPropertiesChanged(() => InvalidCommandsCount, () => ValidCommandsCount, () => CommandsCount);
        }

        /// <summary>
        /// Registration of Command
        /// </summary>
        /// <param name="commandName">Command Name</param>
        /// <param name="command"><see cref="ICommand" /></param>
        public void RegisterCommand(string commandName, ICommand command)
        {
            CommandManager.RegisterCommand(commandName, command);

            OnPropertiesChanged(() => InvalidCommands, () => ValidCommands, () => AllCommands);
            OnPropertiesChanged(() => InvalidCommandsCount, () => ValidCommandsCount, () => CommandsCount);
        }

        #endregion

        #region Add Validation Rule

        /// <summary>
        /// Add Validation Rule
        /// </summary>
        /// <typeparam name="T">Type of Property to Validate</typeparam>
        /// <param name="property">Property <see cref="Expression{TDelegate}" /></param>
        /// <param name="validation">Validation Function <see cref="Func{TResult}" /></param>
        /// <param name="message">Message</param>
        public void RegisterRule<T>(Expression<Func<T>> property, Func<bool> validation, string message = "")
        {
            ValidationManager.RegisterRule<T>(property, validation, message);

            OnPropertiesChanged(() => InvalidValidators, () => ValidValidators, () => AllValidators);
            OnPropertiesChanged(() => InvalidValidatorsCount, () => ValidValidatorsCount, () => ValidatorsCount);
            
        }

        /// <summary>
        /// Add Validation Rule
        /// </summary>
        /// <param name="propertyName">Property Name</param>
        /// <param name="validation">Validation Function <see cref="Func{TResult}" /></param>
        /// <param name="message">Message</param>
        public void RegisterRule(string propertyName, Func<bool> validation, string message = "")
        {
            ValidationManager.RegisterRule(propertyName, validation, message);

            OnPropertiesChanged(() => InvalidValidators, () => ValidValidators, () => AllValidators);
            OnPropertiesChanged(() => InvalidValidatorsCount, () => ValidValidatorsCount, () => ValidatorsCount);
        }

        /// <summary>
        /// Add Validation Rule
        /// </summary>
        /// <param name="propertyName">Property Name</param>
        /// <param name="validationRule">Validation Rule <see cref="ValidationRule" /></param>
        public void RegisterRule(string propertyName, ValidationRule validationRule)
        {
            ValidationManager.RegisterRule(propertyName, validationRule);

            OnPropertiesChanged(() => InvalidValidators, () => ValidValidators, () => AllValidators);
            OnPropertiesChanged(() => InvalidValidatorsCount, () => ValidValidatorsCount, () => ValidatorsCount);
        }

        #endregion

        #region Show Window

        /// <summary>
        /// Show Modal Dialog via WindowServer <see cref="IWindowService" />
        /// </summary>
        /// <param name="window"><see cref="Window" /></param>
        /// <returns><see cref="WindowResult" /></returns>
        public WindowResult ShowDialog(Window window)
        {
            return WindowService.ShowDialog(window);
        }

        /// <summary>
        /// Show Warning Dialog
        /// </summary>
        /// <param name="message">Warning message</param>
        /// <param name="caption">Caption</param>
        public void ShowWarning(string message, string caption)
        {
            WindowService.ShowWarning(message, caption);
        }

        /// <summary>
        /// Show Dialog
        /// </summary>
        /// <param name="window"><see cref="Window" /></param>
        /// <returns><see cref="WindowResult" /></returns>
        public WindowResult Show(Window window)
        {
            return WindowService.Show(window);
        }

        /// <summary>
        /// Show Error Dialog
        /// </summary>
        /// <param name="message">Error message</param>
        public void ShowError(string message)
        {
            WindowService.ShowError(message);
        }

        /// <summary>
        /// Show Error Dialog
        /// </summary>
        /// <param name="message">Error message</param>
        /// <param name="caption">Caption</param>
        public void ShowError(string message, string caption)
        {
            WindowService.ShowError(message, caption);
        }

        /// <summary>
        /// Show Message Dialog
        /// </summary>
        /// <param name="message">Message</param>
        public void ShowMessage(string message)
        {
            WindowService.ShowMessage(message);
        }

        /// <summary>
        /// Show Message Dialog
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="caption">Caption</param>
        public void ShowMessage(string message, string caption)
        {
            WindowService.ShowMessage(message, caption);
        }

        /// <summary>
        /// Show Warning Dialog
        /// </summary>
        /// <param name="warningMessage">Warning message</param>
        public void ShowWarning(string warningMessage)
        {
            WindowService.ShowWarning(warningMessage);
        }

        #endregion

        #region Command Enumerator

        /// <summary>
        /// Commands Enumerator
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <returns>Command <see cref="ICommand" /></returns>
        public ICommand this[string commandName]
        {
            get
            {
                if (CommandManager.IsEmpty)
                {
                    RegisterCommands();
                }

                return CommandManager.GetCommand(commandName);
            }
        }

        #endregion

        #region Addition Helpers

        /// <summary>
        /// Invalid Commands
        /// </summary>
        /// <value>The invalid commands.</value>
        public IReadOnlyCollection<string> InvalidCommands
        {
            get { return CommandManager.InvalidProperties; }
        }

        /// <summary>
        /// All Commands
        /// </summary>
        /// <value>All commands.</value>
        public IReadOnlyCollection<string> AllCommands
        {
            get { return CommandManager.AllProperties; }
        }

        /// <summary>
        /// Valid Commands
        /// </summary>
        /// <value>The valid commands.</value>
        public IReadOnlyCollection<string> ValidCommands
        {
            get { return CommandManager.ValidProperties; }
        }

        /// <summary>
        /// Invalid Settings
        /// </summary>
        /// <value>The invalid settings.</value>
        public IReadOnlyCollection<string> InvalidSettings
        {
            get { return SettingsManager.InvalidProperties; }
        }

        /// <summary>
        /// All Settings
        /// </summary>
        /// <value>All settings.</value>
        public IReadOnlyCollection<string> AllSettings
        {
            get { return SettingsManager.AllProperties; }
        }

        /// <summary>
        /// Valid Settings
        /// </summary>
        /// <value>The valid settings.</value>
        public IReadOnlyCollection<string> ValidSettings
        {
            get { return SettingsManager.ValidProperties; }
        }

        /// <summary>
        /// Invalid Validators
        /// </summary>
        /// <value>The invalid validators.</value>
        public IReadOnlyCollection<string> InvalidValidators
        {
            get { return ValidationManager.InvalidProperties; }
        }

        /// <summary>
        /// All Validators
        /// </summary>
        /// <value>All validators.</value>
        public IReadOnlyCollection<string> AllValidators
        {
            get { return ValidationManager.AllProperties; }
        }

        /// <summary>
        /// Valid Validators
        /// </summary>
        /// <value>The valid validators.</value>
        public IReadOnlyCollection<string> ValidValidators
        {
            get { return ValidationManager.ValidProperties; }
        }

        /// <summary>
        /// Invalid Bad Commands Count
        /// </summary>
        /// <value>The invalid commands count.</value>
        public int InvalidCommandsCount
        {
            get { return InvalidCommands.Count; }
        }

        /// <summary>
        /// Commands Count
        /// </summary>
        /// <value>The commands count.</value>
        public int CommandsCount
        {
            get { return AllCommands.Count; }
        }

        /// <summary>
        /// Valid Commands Count
        /// </summary>
        /// <value>The valid commands count.</value>
        public int ValidCommandsCount
        {
            get { return ValidCommands.Count; }
        }

        /// <summary>
        /// Invalid Settings Count
        /// </summary>
        /// <value>The invalid settings count.</value>
        public int InvalidSettingsCount
        {
            get { return InvalidSettings.Count; }
        }

        /// <summary>
        /// Settings Count
        /// </summary>
        /// <value>The settings count.</value>
        public int SettingsCount
        {
            get { return AllSettings.Count; }
        }

        /// <summary>
        /// Valid Settings Count
        /// </summary>
        /// <value>The valid settings count.</value>
        public int ValidSettingsCount
        {
            get { return ValidSettings.Count; }
        }

        /// <summary>
        /// Invalid Validators Count
        /// It's not a invalid in case of validation, it's invalid in case of fail registration
        /// </summary>
        /// <value>The invalid validators count.</value>
        public int InvalidValidatorsCount
        {
            get { return InvalidValidators.Count; }
        }

        /// <summary>
        /// Validators Count
        /// </summary>
        /// <value>The validators count.</value>
        public int ValidatorsCount
        {
            get { return AllValidators.Count; }
        }

        /// <summary>
        /// Valid Validators Count
        /// It's not a invalid in case of validation, it's invalid in case of fail registration
        /// </summary>
        /// <value>The valid validators count.</value>
        public int ValidValidatorsCount
        {
            get { return ValidValidators.Count; }
        }

        #endregion

        #region Command Exporter

        /// <summary>
        /// Exports the command.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        public void ExportCommands(string fileName)
        {
            var types = GetAllTypes(GetType());

            var commInfoList = new List<CommandInfoAttribute>();

            var methods = new List<MethodInfo>();

            foreach (MethodInfo method in types.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(method => !methods.Contains(method))))
            {
                methods.Add(method);

                commInfoList.AddRange(
                    method.GetCustomAttributes(typeof (CommandInfoAttribute), true)
                        .Cast<CommandInfoAttribute>()
                        .ToList());
            }

            var commInfoHolder = new CommandInfoRoot();
            
            commInfoHolder.CommandInfos.AddRange(commInfoList);

            var serializer = new XmlSerializer(typeof(CommandInfoRoot));
            
            TextWriter textWriter = new StreamWriter(string.IsNullOrWhiteSpace(fileName) ? GetCommandInfoFileName() : fileName);
            
            serializer.Serialize(textWriter, commInfoHolder);
            
            textWriter.Close();   
        }

        /// <summary>
        /// Exports the commands.
        /// </summary>
        public void ExportCommands()
        {
            ExportCommands(GetCommandInfoFileName());
        }

        /// <summary>
        /// Gets the name of the command information file.
        /// </summary>
        /// <returns>System.String.</returns>
        private static string GetCommandInfoFileName()
        {
            string homeFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            string appName = AppDomain.CurrentDomain.FriendlyName;

            homeFolder = Path.Combine(homeFolder, appName);

            if (!Directory.Exists(homeFolder))
            {
                Directory.CreateDirectory(homeFolder);
            }

            return Path.Combine(homeFolder, string.Format("{0}.{1}.Commands.xml", Environment.UserName, appName));
        }

        /// <summary>
        /// Gets all types.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>IEnumerable{Type}.</returns>
        private static IEnumerable<Type> GetAllTypes(Type type)
        {
            var list = new List<Type>();

            if (type == typeof(ViewModelTop))
            {
                return list;
            }

            if (type == typeof(MainViewModel))
            {
                return list;
            }

            list.Add(type);

            list.AddRange(GetAllTypes(type.BaseType));

            return list;

        }

        #endregion

    }
}